#include "TTSCaller.h"
#include "FileDescriptor.h"
#include "shared_ptr.hpp"

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdio>

using namespace std;

void TTSCaller::SetTTSReq(string text, LAN lan, ENCODING encoding)
{
    _text = text;
    _lan = lan;
    _encoding = encoding;
}

bool TTSCaller::IsValidConnAns(const TTSDataGram* datagram)
{
    const uint32 jTTS_ANS_CONN_LEN = 16;
    if (datagram == NULL || datagram->GetID() != jTTS_ID
        || datagram->GetCommand() != jTTS_ANS_CONNECT
        || datagram->GetBodyLen() + jTTS_HEADER_LEN != jTTS_ANS_CONN_LEN)
    {
        return false;
    }

    const byte* body = datagram->GetBody();
    const uint32 errorCode = body[0] << 24 | body[1] << 16 | body[2] << 8 | body[3];

    printf("ConnectTTS: id=[%x], cmd=[%x], bodylen=[%u], errno=[%u]\n",
        datagram->GetID(), datagram->GetCommand(), datagram->GetBodyLen(),
        errorCode);
    return errorCode == jTTS_NOERROR;
}

bool TTSCaller::Connect(int fd)
{
    if (fd < 0)
    {
        return false;
    }

    sockaddr_in addr;
    memset(&addr, 0, sizeof(sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(_ttsSvrPort);
    addr.sin_addr.s_addr = inet_addr(_ttsSvrIP.c_str());

    if (connect(fd, (sockaddr*)&addr, sizeof(sockaddr)) < 0)
    {
        return false;
    }

    boost::shared_ptr<TTSDataGram> datagram(ReadTTSDatagram(fd));
    return IsValidConnAns(datagram.get());
}

TTSDataGram* TTSCaller::ReadTTSDatagram(int fd)
{
    if (fd < 0)
    {
        return NULL;
    }

    byte header[jTTS_HEADER_LEN];
    memset(header, 0, jTTS_HEADER_LEN);

    int n = readn(fd, header, jTTS_HEADER_LEN);
    if (n != jTTS_HEADER_LEN)
    {
        return NULL;
    }

    uint32 id = header[0] << 24 | header[1] << 16 | header[2] << 8 | header[3];
    uint32 cmd = header[4] << 24 | header[5] << 16 | header[6] << 8 | header[7];
    uint32 len = header[8] << 24 | header[9] << 16 | header[10] << 8 | header[11];
    if (len < 12 || id != jTTS_ID)
    {
        return NULL;
    }

    uint32 bodyLen = len - jTTS_HEADER_LEN;

    TTSDataGram* datagram = new TTSDataGram();
    datagram->SetID(id);
    datagram->SetCommand(cmd);
    datagram->SetBodyAndLen(bodyLen);

    n = readn(fd, datagram->GetBody(), bodyLen);
    if (n != bodyLen)
    {
        delete datagram;
        return NULL;
    }

    return datagram;
}

TTSParams* TTSCaller::MakeTTSParams(LAN lan, ENCODING encoding)
{
    if (encoding != GBK)
    {
        return NULL;
    }
    TTSParams* params = new TTSParams();
    memset(params, 0, sizeof(TTSParams));

    params->Id = jTTS_ID;
    params->Command = jTTS_REQ_SETPARAM4;
    params->Len = sizeof(TTSParams);
    params->Version = 4;
    params->CodePage = 936; // GBK

    const char* jTTS_VOICE_XIAOKUN_CH_FEMALE = "84316E85-143E-4410-B00B-9DF681684C6C";
    const char* jTTS_VOICE_JULIE_EN_FEMALE = "0E81DAEB-C858-4590-86CE-F0937FA71CD9";
    strncpy(params->VoiceGuid,
        lan == ZH ? jTTS_VOICE_XIAOKUN_CH_FEMALE : jTTS_VOICE_JULIE_EN_FEMALE,
        sizeof(params->VoiceGuid));

    printf("LAN = [%u], VoiceGUID = [%s]\n", lan, params->VoiceGuid);

    params->Pitch = 5;
    params->Volume = 9;
    params->Speed = 5;
    params->VoiceBufSize = 128;
    params->InsertInfoSize = 100;
    params->Format = 0;

    return params;
}

bool TTSCaller::IsValidSetParamsAns(const TTSDataGram* datagram)
{
    const uint32 jTTS_ANS_CONN_LEN = 16;
    if (datagram == NULL || datagram->GetID() != jTTS_ID
        || datagram->GetCommand() != jTTS_ANS_SETPARAM
        || datagram->GetBodyLen() + jTTS_HEADER_LEN != jTTS_ANS_CONN_LEN)
    {
        return false;
    }

    const byte* body = datagram->GetBody();
    const uint32 errorCode = body[0] << 24 | body[1] << 16 | body[2] << 8 | body[3];

    printf("SetParamsTTS: id=[%x], cmd=[%x], bodylen=[%u], errno=[%u]\n",
        datagram->GetID(), datagram->GetCommand(), datagram->GetBodyLen(),
        errorCode);
    return errorCode == jTTS_NOERROR;
}

TTSText2AudioReq* TTSCaller::MakeText2AudioReq(const char* text)
{
    if (text == NULL || strlen(text) == 0 || strlen(text) > MAX_TEXT_BUF_LEN)
    {
        return NULL;
    }

    uint32 textLen = strlen(text);
    TTSText2AudioReq* text2audioReq = new TTSText2AudioReq();
    text2audioReq->Id = jTTS_ID;
    text2audioReq->Command = jTTS_REQ_TEXT4;
    text2audioReq->Len = 16 + textLen;
    text2audioReq->TextLen = textLen;
    strncpy(text2audioReq->Text, text, textLen);

    return text2audioReq;
}

int TTSCaller::RecvAudio(int fd, Item* item)
{
    if (fd < 0 || item == NULL)
    {
        return -1;
    }

    uint32 cnt = 0;
    uint32 bitsPerSample = 16;
    uint32 sampleRate = 16000;
    uint32 errorCode = jTTS_NOERROR;
    item->_dataLen = WAV_HEADER_LEN;

    do
    {
        boost::shared_ptr<TTSDataGram> datagram(ReadTTSDatagram(fd));
        printf("RecvAudio: ID=[%x], Command=[%x], BodyLen=[%u]\n", datagram.get()->GetID(),
            datagram.get()->GetCommand(), datagram.get()->GetBodyLen());
        if (datagram.get() == NULL || datagram.get()->GetID() != jTTS_ID
            || datagram.get()->GetCommand() != jTTS_ANS_SOUND4
            || datagram.get()->GetBodyLen() < 24)
        {
            cout << "RecvAudio: Failed to recv datagram" << endl;
            return -1;
        }

        byte* body = datagram.get()->GetBody();
        errorCode = (body[0] << 24) | (body[1] << 16) | (body[2] << 8) | body[3];
        uint32 t_bitsPerSample = (body[4] << 24) | (body[5] << 16) | (body[6] << 8) | body[7];
        uint32 t_samplesPerSec = (body[8] << 24) | (body[9] << 16) | (body[10] << 8) | body[11];
        uint32 t_soundLen = (body[12] << 24) | (body[13] << 16) | (body[14] << 8) | body[15];
        uint32 t_insertInfoNum = (body[16] << 24) | (body[17] << 16) | (body[18] << 8) | body[19];
        uint32 t_readBytes = (body[20] << 24) | (body[21] << 16) | (body[22] << 8) | body[23];

        printf("RecvAudio: errorcode=[%x], bitsPerSample=[%u], samplesPerSec=[%u], soundLen=[%u],"
                "insertInfoNum=[%u], readbytes=[%u]\n", errorCode, t_bitsPerSample,
                t_samplesPerSec, t_soundLen, t_insertInfoNum, t_readBytes);
        if (item->_dataLen + t_soundLen > MAX_BUF_LEN
            || (errorCode != jTTS_NOERROR && errorCode != jTTS_MOREDATA))
        {
            return -1;
        }
        if (errorCode == jTTS_NOERROR && t_soundLen == 0)
        {
            break;
        }

        memcpy(item->_data + item->_dataLen, body + 24, t_soundLen);
        item->_dataLen += t_soundLen;
        if (cnt == 0)
        {
            bitsPerSample = t_bitsPerSample;
            sampleRate = t_samplesPerSec;
        }
        cnt++;
    }
    while (errorCode == jTTS_MOREDATA);

    // TTS默认返回大端数据，WAV标准使用小端
    // Reformat(item, bitsPerSample);
    AddWavHeader(item, bitsPerSample, sampleRate, item->_dataLen - WAV_HEADER_LEN);

    return 0;
}

void TTSCaller::AddWavHeader(Item* item, uint32 bitsPerSample, uint32 sampleRate, uint32 dataLen)
{
    if (item == NULL)
    {
        return;
    }

    // Reference : https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
    const char* chunkID = "RIFF";
    const uint32 chunkSize = 36 + dataLen;
    const char* format = "WAVE";
    const char* subChunk1ID = "fmt ";
    const uint32 subChunk1Size = 16;
    const uint16 audioFormat = 1;
    const uint16 numChannels = 1;
    const uint32 byteRate = sampleRate * numChannels * bitsPerSample / 8;
    const uint16 blockAlign = numChannels * bitsPerSample / 8;
    const char* subChunk2ID = "data";
    const uint32 subChunk2Size = dataLen;

    uint32 len = 0;
    memcpy(item->_data + len, chunkID, strlen(chunkID));
    len += strlen(chunkID);

    item->_data[len++] = chunkSize & 0x000000FF;
    item->_data[len++] = (chunkSize >> 8) & 0x000000FF;
    item->_data[len++] = (chunkSize >> 16) & 0x000000FF;
    item->_data[len++] = (chunkSize >> 24) & 0x000000FF;

    memcpy(item->_data + len, format, strlen(format));
    len += strlen(format);

    memcpy(item->_data + len, subChunk1ID, strlen(subChunk1ID));
    len += strlen(subChunk1ID);

    item->_data[len++] = subChunk1Size & 0x000000FF;
    item->_data[len++] = (subChunk1Size >> 8) & 0x000000FF;
    item->_data[len++] = (subChunk1Size >> 16) & 0x000000FF;
    item->_data[len++] = (subChunk1Size >> 24) & 0x000000FF;

    item->_data[len++] = (audioFormat) & 0x000000FF;
    item->_data[len++] = (audioFormat >> 8) & 0x000000FF;

    item->_data[len++] = (numChannels) & 0x000000FF;
    item->_data[len++] = (numChannels >> 8) & 0x000000FF;

    item->_data[len++] = sampleRate & 0x000000FF;
    item->_data[len++] = (sampleRate >> 8) & 0x000000FF;
    item->_data[len++] = (sampleRate >> 16) & 0x000000FF;
    item->_data[len++] = (sampleRate >> 24) & 0x000000FF;

    item->_data[len++] = byteRate & 0x000000FF;
    item->_data[len++] = (byteRate >> 8) & 0x000000FF;
    item->_data[len++] = (byteRate >> 16) & 0x000000FF;
    item->_data[len++] = (byteRate >> 24) & 0x000000FF;

    item->_data[len++] = (blockAlign) & 0x000000FF;
    item->_data[len++] = (blockAlign >> 8) & 0x000000FF;

    item->_data[len++] = (bitsPerSample) & 0x000000FF;
    item->_data[len++] = (bitsPerSample >> 8) & 0x000000FF;

    memcpy(item->_data + len, subChunk2ID, strlen(subChunk2ID));
    len += strlen(subChunk2ID);

    item->_data[len++] = subChunk2Size & 0x000000FF;
    item->_data[len++] = (subChunk2Size >> 8) & 0x000000FF;
    item->_data[len++] = (subChunk2Size >> 16) & 0x000000FF;
    item->_data[len++] = (subChunk2Size >> 24) & 0x000000FF;

    cout << "AddWavHeader: len = " << len << endl;
}

void TTSCaller::Reformat(Item* item, uint32 bitsPerSample)
{
    if (item == NULL || bitsPerSample != 16)
    {
        return;
    }

    for (int i = 0; i < item->_dataLen - 1; i += 2)
    {
        byte b = item->_data[i];
        item->_data[i] = item->_data[i + 1];
        item->_data[i + 1] = b;
    }
}

int TTSCaller::TalkWithTTS(Item* item)
{
    FileDescriptor sockfd(socket(AF_INET, SOCK_STREAM, 0));
    int fd = sockfd.FD();

    if (fd < 0 || item == NULL)
    {
        return -1;
    }

    if (!Connect(fd))
    {
        cout << "Failed to connect" << endl;
        return -1;
    }

    boost::shared_ptr<TTSParams> params(MakeTTSParams(_lan, _encoding));
    params.get()->hton();

    printf("sizeof(TTSParams) = [%u]\n", sizeof(TTSParams));
    int n = writen(fd, (const byte*)params.get(), sizeof(TTSParams));
    if (n != sizeof(TTSParams))
    {
        cout << "Failed to send params" << endl;
        return -1;
    }

    boost::shared_ptr<TTSDataGram> datagram(ReadTTSDatagram(fd));
    if (!IsValidSetParamsAns(datagram.get()))
    {
        cout << "Failed to set params\n" << endl;
        return -1;
    }

    boost::shared_ptr<TTSText2AudioReq> text2audioReq(MakeText2AudioReq(_text.c_str()));
    if (text2audioReq.get() == NULL)
    {
        cout << "Failed to make text2audio request" << endl;
        return -1;
    }

    uint32 lenSent = text2audioReq.get()->Len;
    text2audioReq.get()->hton();
    n = writen(fd, (byte*)text2audioReq.get(), lenSent);
    if (n != lenSent)
    {
        cout << "Failed to send text2audio request" << endl;
        return -1;
    }
    cout << "SendText2AudioReq: lenSent = " << lenSent << endl;

    return RecvAudio(fd, item);
}
